孰优孰劣,gRPC和Rest性能大比拼
本文通过示例比较REST和gRPC性能。
我们在项目中使用Spring Boot和REST控制器。在本文中,我们将比较REST和gRPC,以探讨是否可以使用gRPC进行性能优化。
gRPC是一种RPC(远程过程调用)方法,通过使用Google Protobuf序列化基础架构,提供应用程序间的通信。
gRPC依赖于HTTP/2协议,而REST使用HTTP/1协议。 在HTTP/1中,每个请求需要建立一个TCP连接,而在HTTP/2中可以重用同一个TCP连接。 在HTTP/1中,头部以纯文本形式发送,而在HTTP/2中,头部和数据都被转换为二进制数据。 在HTTP/2中,服务器可以对客户端请求发送多个响应消息。 由于通信是通过二进制数据进行的,因此消息占用的空间更小,提供了高效的序列化和反序列化过程。
客户端和服务器应用程序可以使用不同的语言实现,并通过.proto文件进行通信。
这是在实现gRPC服务器-客户端应用程序时使用的示例proto定义。
syntax = "proto3";
package order.core;
// 生成的源代码选项
option java_package = "com.example.grpc.order";
option java_multiple_files = true;
message OrderRequest {
int64 timestamp = 1;
int32 count = 2;
double amount = 3;
string name = 4;
bool active = 5;
Type type = 6;
InnerObject innerObject = 7;
repeated string items = 8;
reserved 9;
}
enum Type {
UNKNOWN = 0;
WEB = 1;
LOCAL = 2;
}
message InnerObject {// Level 1
Inner inner = 1;
}
message Inner {// Level 2
int64 value = 1;
bool active = 2;
}
message OrderResponse {
int64 timestamp = 1;
string message = 2;
}
service OrderService {
rpc order (OrderRequest) returns (OrderResponse);
}
1 支持的数据类型
下表显示了支持的数据类型及其对应的Java类型:
2 保留字段
在使用proto定义时,向后兼容性是一个重要的概念。如果你删除一个字段但没有指定先前使用的编号,其他用户可以使用相同的编号来表示不同的字段,这可能会导致严重的问题。为解决这个问题,你可以指定已删除字段的字段编号为保留字段。如果任何用户尝试使用这些字段标识符,协议缓冲区编译器将会报错。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
对于Java gRPC实现,编译器会为每个消息类型生成必要的类文件和构建器。
3 实现
为了进行比较,我这里实现了一个REST服务器-客户端和gRPC服务器-客户端。
4 REST服务器实现
创建一个Spring Boot启动器项目,并编写如下的REST控制器类。使用默认的Tomcat配置。
@RestController
@RequestMapping
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@PostMapping(path = "/order")
public OrderResponse order(@RequestBody OrderRequest request) {
OrderDto orderDto = convertOrderDto(request);
// 业务逻辑,创建订单
return orderService.createOrder(orderDto);
}
}
5 REST客户端实现
使用“Feign Client”实现REST客户端,代码如下:
@FeignClient(name = "orderService", url = "${integration.rest.url}")
public interface OrderFeignClient {
@PostMapping(value = "/order")
OrderResponse order(@RequestBody OrderRequest request);
}
@Service
@RequiredArgsConstructor
public class OrderRestClient {
private final OrderFeignClient orderFeignClient;
public OrderResponse createOrder(OrderRequest orderRequest) {
OrderResponse response = orderFeignClient.order(orderRequest);
return response;
}
}
6 gRPC服务器实现
在服务器实现中,这里使用grpc-server-spring-boot-starter以及grpc-protobuf和grpc-stub依赖项。gRPC支持一元、客户端流、服务器流和双向流API类型。我实现了一个一元API,它从服务器发送请求并获取响应。
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.51.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.51.0</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
在pom文件中添加以下构建步骤:
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.19.2:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.51.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
编译器会为proto文件中的每个消息类型生成一个.java文件和一个相应的类。该插件将在项目的编译步骤中触发。
@GrpcService
注解用于服务器实现。默认情况下,gRPC服务器将监听9090
端口。端口设置可以通过应用程序.yaml文件中的grpc.server.port
属性更改为其他值。
在业务层中,这里设计了gRPC服务器和REST服务器都调用同一个orderService。
@GrpcService
@RequiredArgsConstructor
public class OrderGrpcController extends OrderServiceGrpc.OrderServiceImplBase {
private final OrderService orderService;
@Override
public void order(OrderRequest request, StreamObserver<OrderResponse> responseObserver) {
OrderDto orderDto = convertOrderDto(request);
// 业务逻辑,创建订单
com.example.grpcserverapi.response.OrderResponse orderServiceResponse = orderService.createOrder(orderDto);
OrderResponse response = OrderResponse.newBuilder()
.setTimestamp(orderServiceResponse.getTimestamp())
.setMessage(orderServiceResponse.getMessage())
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
7 gRPC客户端实现
在客户端实现中,这里使用了grpc-client-spring-boot-starter以及grpc-protobuf和grpc-stub 依赖项。
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.51.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.51.0</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
在应用程序.yaml文件中添加gRPC服务器的URL,如下所示:
grpc:
client:
orderService:
address: static://本地主机:9090
negotiation-type: plaintext
@GrpcClient(serverName)
字段注解用于gRPC客户端存根实现。不要使用@Autowired
注解进行客户端实现。
@Service
@RequiredArgsConstructor
public class OrderGrpcClient {
@GrpcClient("orderService")
private OrderServiceGrpc.OrderServiceBlockingStub blockingStub;
public OrderResponse createOrder(OrderRequest request) {
return blockingStub.order(request);
}
}
当向REST客户端和gRPC客户端分别发送10,000个请求进行性能测试时,能得到以下结果:
gRPC平均每个请求的时间:0.3503毫秒
REST平均每个请求的时间:2.3109毫秒
请注意,这是在本地机器上进行性能测试的。
8 结论
根据性能结果,使用gRPC实现可以将延迟降低85%。
如果你希望服务之间通信更快,可以使用gRPC代替REST。由于通信是基于二进制数据进行的,如果服务频繁更改,维护兼容性会很困难。在这种情况下,REST可能更可取。
推荐书单
《Java Web从入门到精通(第3版)》
《Java Web从入门到精通(第3版)》从初学者角度出发,通过通俗易懂的语言、丰富多彩的实例,详细介绍了进行JavaWeb应用程序开发需要掌握的各方面技术。
本书共分21章,包括JavaWeb应用开发概述、HTML与CSS网页开发基础、JavaScript脚本语言、搭建开发环境、JSP基本语法、JSP内置对象、JavaBean技术、Servlet技术、过滤器和监听器、JavaWeb的数据库操作、EL(表达式语言)、JSTL标签、Ajax技术、Struts2基础、Struts2高级技术、Hibemate技术、Hibemate高级应用、Spring核心之IoC、Spring核心之AOP、SSM框架整合开发、九宫格记忆网等内容。
本书所有知识都结合具体实例进行介绍,涉及的程序代码给出了详细的注释,可以使读者轻松领会JavaWeb应用程序开发的精髓,快速提高开发技能。 另外,除了纸质内容之外,配套资源中还给出了海量开发资源库,主要内容如下:
语音视频讲解:总时长19小时,共94段 模块资源库:15个经典模块开发过程完整展现 测试题库系统:596道能力测试题目 PPT电子教案 实例资源库:1010个实例及源码详细分析 项目案例资源库:15个企业项目开发过程完整展现 面试资源库:369个企业面试真题
《Java Web从入门到精通(第3版)》可作为软件开发入门者的自学用书,也可作为高等院校相关专业的教学参考书,还可供开发人员查阅、参考。
购买链接:https://item.jd.com/13446953.html
精彩回顾